#include "pricegrabber.h"

#include "invalidprice.h"

#include <QtWebKit/QWebPage>
#include <QtWebKit/QWebFrame>
#include <QtWebKit/QWebElement>

#include <QRegExp>
#include <QFileInfo>
#include <QSettings>
#include <QLocale>

#include <QApplication>

#include <assert.h>


QQueue<PriceGrabber*> PriceGrabber::m_queue;
QSet<PriceGrabber*> PriceGrabber::m_batch;
QSet<QUrl> PriceGrabber::m_batchUrls;

int PriceGrabber::m_maxBatchSize = 4;


QMap<QString, int> PriceGrabber::m_cache;
QMap<QString, QTime> PriceGrabber::m_times;

int PriceGrabber::m_maxCacheTime = 300;


PriceGrabber *PriceGrabber::queue(const QUrl &url)
{
    PriceGrabber *priceGrabber(new PriceGrabber(url));
    m_queue << priceGrabber;

    return priceGrabber;
}

void PriceGrabber::triggerQueue()
{
    if(m_queue.isEmpty())
        return;

    QApplication::processEvents();

    while(!m_queue.isEmpty() && m_batch.size() < m_maxBatchSize
        && !m_batchUrls.contains(m_queue.first()->m_url))
    {
        PriceGrabber *priceGrabber = m_queue.takeFirst();

        m_batch << priceGrabber;
        m_batchUrls << priceGrabber->m_url;

        priceGrabber->start();
    }
}

void PriceGrabber::unbatch(
    PriceGrabber *priceGrabber
,   const bool retriggerQueue)
{  
    if(!m_batch.contains(priceGrabber))
        return;

    m_batch.remove(priceGrabber);
    m_batchUrls.remove(priceGrabber->m_url);

    if(retriggerQueue)
        triggerQueue();
}

void PriceGrabber::dequeue(
    PriceGrabber *priceGrabber
,   const bool retriggerQueue)
{
    if(!m_queue.contains(priceGrabber))
        return;

    m_queue.removeOne(priceGrabber);

    if(retriggerQueue)
        triggerQueue();
}

void PriceGrabber::cache(
    PriceGrabber *priceGrabber
,   const int price)
{
    const QString url(priceGrabber->m_url.toString());

    m_cache[url] = price;
    m_times[url] = QTime::currentTime();
}


PriceGrabber::PriceGrabber(const QUrl &url)
:   QObject()
,   m_page(NULL)
,   m_frame(NULL)
,   m_url(url)
{
}

PriceGrabber::~PriceGrabber()
{
    dequeue(this, false);
    unbatch(this, false);
}

void PriceGrabber::start()
{
    // check cache

    const QString url(m_url.toString());

    if(m_cache.contains(url)
    && m_times[url].secsTo(QTime::currentTime()) < m_maxCacheTime)
    {
        emit grabSucceeded(m_cache[url]);
        // don't cache

        unbatch(this);
        return;
    }

    // initiate grabbing

    m_page = new QWebPage();
    m_frame = m_page->mainFrame();

    connect(m_frame, SIGNAL(loadFinished(bool))
        , this, SLOT(me_loadFinished(bool)));

    m_frame->load(m_url);
}

void PriceGrabber::stop()
{
    if(m_frame)
        disconnect(m_frame, SIGNAL(loadFinished(bool))
            , this, SLOT(me_loadFinished(bool)));

    if(m_page)
        m_page->deleteLater();

    dequeue(this, false);
    unbatch(this, false);
}

const bool PriceGrabber::grab(int &price)
{
    QString host(m_url.host().toLower());
    if(host.isEmpty())
    {
        const QString error = tr("Empty host in url \"%1\".")
            .arg(m_url.toString());

        emit grabFailed(error);
        return false;
    }

    QString grabSelector;
    QString grabPriceRE;

    retrieveGrab(host, grabSelector, grabPriceRE);
    if(grabSelector.isEmpty() || grabPriceRE.isEmpty())
        return false;

    return retrievePrice(grabSelector, grabPriceRE, price);
}

void PriceGrabber::retrieveGrab(
    const QString &host
,   QString &grabSelector
,   QString &grabPriceRE)
{  
    grabSelector = QString();
    grabPriceRE = QString();

    QFileInfo fi(QString("grabber/%1.grab").arg(host));
    if(!fi.exists())
    {
        const QString error = tr("Unknown host \"%1\" (%2 does not exist).")
            .arg(host).arg(fi.absoluteFilePath());

        emit grabFailed(error);
        return;
    }

    // load css selector and price regexp from file

    QSettings s(fi.absoluteFilePath(), QSettings::IniFormat);

    s.beginGroup("EinkaufsListe-PriceGrabber");


    const QString grabAuthority(s.value("Host", "unknown").toString());

    if(grabAuthority.compare(host) != 0)
    {
        const QString error = tr("Grabbed host \"%1\" missmatches \"%2\" in %3.")
            .arg(grabAuthority).arg(host).arg(fi.absoluteFilePath());

        emit grabFailed(error);
        return;
    }

    grabSelector = s.value("CSS2Selector").toString();
    grabPriceRE  = s.value("PriceRegExp" ).toString();

    s.endGroup();
}

const bool PriceGrabber::retrievePrice(
    const QString &grabSelector
,   const QString &grabPriceRE
,   int &price)
{
    QWebElement e(m_frame->findFirstElement(grabSelector));

    if(e.isNull())
    {
        const QString error = tr("No element was found with CSS2 selector \"%1\".")
            .arg(grabSelector);

        emit grabFailed(error);
        return false;
    }

    QRegExp priceRE(grabPriceRE);

    if(!priceRE.isValid())
    {
        const QString error = tr("\"%1\" is no valid regular expression.")
            .arg(grabPriceRE);

        emit grabFailed(error);
        return false;
    }

    priceRE.indexIn(e.toPlainText());

    if(priceRE.captureCount() != 2)
    {
        const QString error = tr("\"%1\" yields unknown price format.")
            .arg(grabPriceRE);

        emit grabFailed(error);
        return false;
    }

    price = priceRE.capturedTexts()[1].toInt() * 100 + priceRE.capturedTexts()[2].toInt();

    return true;
}

void PriceGrabber::me_loadFinished(bool ok)
{
    unbatch(this);

    if(!ok)
    {
        const QString error = tr("Webpage could not be loaded.");

        emit grabFailed(error);
        return;
    }

    int price = INVALID_PRICE;

    if(grab(price))
    {
        cache(this, price);
        emit grabSucceeded(price);
    }
}
